/* QuodeJSH version 0.2.1
 * Unobtrusive XHTML-compliant Javascript Syntax Highlighter
 * (c) 2007 www.quode.net
 * QuodeJSH is licensed under the terms of the MIT License
 *---------------------------------------------------------*/

if (typeof Quode == "undefined") {
  var Quode = {};
}

Quode.JSH = {
  
  version: "0.2.1",
  
  inCssPrefix: "jsh:",
  outCssPrefix: "jsh-",
  
  //arg := tagName | HTMLElement | [HTMLElement, ..]
  highlight: function(t) {
    if (typeof t == "string") this.process(document.getElementsByTagName(t));
    else if (t.length) this.process(t);
    else this.process([t]);
  },  
    
  process: function(a) {
    var i = 0;
    function parse() {
      var t = new Date().getTime() + 250;
      for (; i < a.length && new Date().getTime() < t; ++i) {
        var e = a[i];
        if (e.parentNode && e.className.length) {
          var l, n, s = "", r = RegExp(Quode.JSH.inCssPrefix+"(\\w+)", "i").exec(e.className);
          if (r && r[1] && (l = Quode.JSH.language.registered[r[1]]) && (n = e.firstChild)) {      
            do s += n.nodeValue; while (n = n.nextSibling);
            s = s.replace(/^[\n\r]+|\s+$/g, "");
            if (s.length) {
              var p = new Quode.JSH.Parser(s, l);
              p.parse();
              Quode.JSH.build(e, p, Quode.JSH.language.registeredClass[r[1]]);
            }
          }
        }
        if (i < a.length) setTimeout(parse, 250);
      }
    }
    parse();
  },
  
  Parser: function(str, lang, embedCss) {
    this.str = str;
    this.marks = [];
    this.match = "";
    this.capture = {};
    var self = this, outerOffset = 0, pats;
    this.parse = function(marks, offset, par) {
      if (marks && offset !== undefined) {
        self.marks = marks;
        outerOffset = offset;
      }
      if (par) pats = [{regex: par.until, nested: par.nested, css: par.css, exit: 1}].concat(lang.patterns);
      else pats = lang.patterns;
      for (var i = 0, z = self.str.length, g = pats.length; i < z; ++i) {
        var s = [], k, r, l;
        for (k = 0; k < g; ++k)
          if ((r = pats[k].regex.exec(self.str.substr(i))) && !s[r.index]) {
            s[r.index] = { pat: pats[k], match: r };
            if (r.index == 0) break;
          }
        for (k = 0, l = s.length; k < l; ++k) {
          if (!s[k]) continue;
          var localOffset = i + k, c = s[k].pat;
          if (c.capture) self.capture[c.capture] = s[k].match[0];
          var len = s[k].match[0].length, currentOffset = localOffset + outerOffset;
          if (c.exit) {
            self.match = s[k].match[0];
            if (c.nested) {
              self.marks[currentOffset] = new Quode.JSH.Mark(len, c.css, embedCss);
              return localOffset + len;
            }
            return localOffset;
          }
          if (!c.nested) self.marks[currentOffset] = new Quode.JSH.Mark(len, c.css, embedCss);
          else len = 0;
          if (c.embed) len = embed(c, len, localOffset, currentOffset);
          i = localOffset + len - 1;
          break;
        }
      }
      function embed(c, len, localOffset, currentOffset) {
        var p, ec = "", e = c.embed;
        if (typeof e == "string") {
          ec = e;
          e = Quode.JSH.language.registered[e];
        }
        if (e && (p = new Quode.JSH.Parser(self.str.substr(localOffset + len), e, ec))) {
          var h = 0, q = p.parse(self.marks, currentOffset + len, c);
          if (!c.nested && c.included !== false) {
            h = p.match.length;
            self.marks[currentOffset + len + q] = new Quode.JSH.Mark(h, c.css);
          }
          len += q + h;
          if (c.captured) {
            for (var a = 0, l = c.captured.length; a < l; ++a) {
              var t, cont = false;
              for (var b in c.captured[a].test) {
                t = c.captured[a].test[b];
                if (typeof t == "boolean") {
                  if (t === false && p.capture[b] != undefined ||
                      t === true && p.capture[b] == undefined) {
                    cont = true;
                    break;
                  }
                }
                else if (!t.exec(p.capture[b])) {
                  cont = true;
                  break;
                }
              }
              if (cont) continue;
              len = embed(c.captured[a], len, localOffset , currentOffset);
              break;
            }
          }
        }
        else if (c.until && (r = c.until.exec(self.str.substr(localOffset + len)))) {
          var pos = len;
          if (c.included === false) len += r.index;          
          else len += r.index + r[0].length;
          self.marks[currentOffset + pos] = new Quode.JSH.Mark(len - pos, c.css);
        }
        return len;
      }
      return z;
    }
  },
  
  build: function(e, parser, className) {
    var s = parser.str, m = parser.marks, k = 0, n, c = document.createElement(e.nodeName);
    var embed = false;
    var C = c;
    for (var i = 0, l = parser.marks.length; i < l; ++i) {
      if (!m[i]) continue;
      if (i > k) c.appendChild(document.createTextNode(s.substring(k, i)));
      n = document.createElement("span");
      n.className = m[i].css;
      if (!embed) {
        if (m[i].embed) {
          embed = true;
          C = c;
          c = document.createElement("span");
          c.className = m[i].embed;
        }
      }
      else {
        if (!m[i].embed) {
          embed = false;
          C.appendChild(c);
          c = C;
        }
      }
      n.appendChild(document.createTextNode(s.substr(i, m[i].length)));
      c.appendChild(n);
      k = i + (m[i].length ? m[i].length : 1);
      i = k - 1;
    }
    if (k < s.length) c.appendChild(document.createTextNode(s.substr(k)));
    if (c != C) {
      C.appendChild(c);
      c = C;
    }
    c.className = this.outCssPrefix + className;
    e.parentNode.replaceChild(c, e);
  },
  
  language: {
    // language object, alias str|array, parent css class
    register: function(l, a, c) {
      if (typeof a == "string") { 
        this.registered[a] = l;
        this.registeredClass[a] = c ? c : a;
      }
      else {
        c = c || a[0];
        for (var i = 0; i < a.length; ++i) {
          this.registered[a[i]] = l;
          this.registeredClass[a[i]] = c;
        }
      }
    },
    registered: { },
    registeredClass: { }
  },
  
  Mark: function(l, c, e) {
    this.length = l;
    this.css = c;
    this.embed = e;
  },
  
  util: {
    regex: { 
      multiLComment: new RegExp("/\\*[\\s\\S]*?\\*/"),
      singleLComment: new RegExp("//.*?$", "m"),
      perlComment: new RegExp("#.*?$", "m"),
      doubleQString: new RegExp('"[^"\\r\\n\\\\]*(\\\\.[^"\\r\\n\\\\]*)*"'),
      singleQString: new RegExp("'[^'\\r\\n\\\\]*(\\\\.[^'\\r\\n\\\\]*)*'"),
      doubleQStringLB: new RegExp('"[^"\\\\]*(\\\\.[^"\\\\]*)*"'),
      singleQStringLB: new RegExp("'[^'\\\\]*(\\\\.[^'\\\\]*)*'"),
      number: new RegExp("\\b(?:0x[0-9a-f]+|[0-9]+(?:\\.[0-9]+)?(?:e[+-]?[0-9]+)?(?:ul|[ulf])?)\\b","i"),
      xmlComment: new RegExp("<!--[\\s\\S]*?-->"),
      xmlCDATA: new RegExp("<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"),
      xmlDoctype: new RegExp("<![\\s\\S]*?>"),
      xmlPI: new RegExp("<\\?[\\s\\S]*?\\?>"),
      xmlEntity: new RegExp("&[#a-z0-9]+;", "i"),
      keywords: function(s, m) { return new RegExp("\\b(?:"+s.replace(/\s+/g,"|")+")\\b",m); }
    }
  }
  
}





